/*******************************************************************************
* Copyright (c) 2011 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package com.ibm.wala.cast.js.test;
import java.io.File;
import java.io.IOException;
import java.util.regex.Pattern;
import org.junit.Assert;
import org.junit.ComparisonFailure;
import org.junit.Ignore;
import org.junit.Test;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.ClosureExtractor;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.ForInBodyExtractionPolicy;
import com.ibm.wala.cast.tree.CAstEntity;
import com.ibm.wala.cast.tree.impl.CAstImpl;
import com.ibm.wala.classLoader.SourceFileModule;
import com.ibm.wala.classLoader.SourceModule;
import com.ibm.wala.util.io.FileUtil;
public abstract class TestForInBodyExtraction {
public void testRewriter(String in, String out) {
testRewriter(null, in, out);
}
/* The translation to CAst introduces temporary names based on certain characteristics of the translation
* process. This sometimes makes it impossible to precisely match up the results of first translating to
* CAst and then transforming, and first transforming the JavaScript and then translating to CAst.
*
* As a heuristic, we replace some generated names with placeholders, which will be the same in both
* versions. This could in principle mask genuine errors.
*/
public static String eraseGeneratedNames(String str) {
Pattern generatedNamePattern = Pattern.compile("\\$\\$destructure\\$(rcvr|elt)\\d+");
str = generatedNamePattern.matcher(str).replaceAll("\\$\\$destructure\\$$1xxx");
Pattern generatedFunNamePattern = Pattern.compile("\\.js(@\\d+)+");
str = generatedFunNamePattern.matcher(str).replaceAll(".js@xxx");
return str;
}
public void testRewriter(String testName, String in, String out) {
File tmp = null;
String expected = null;
String actual = null;
try {
tmp = File.createTempFile("test", ".js");
FileUtil.writeFile(tmp, in);
CAstImpl ast = new CAstImpl();
actual = new CAstDumper().dump(new ClosureExtractor(ast, ForInBodyExtractionPolicy.FACTORY).rewrite(parseJS(tmp, ast)));
actual = eraseGeneratedNames(actual);
FileUtil.writeFile(tmp, out);
expected = new CAstDumper().dump(parseJS(tmp, ast));
expected = eraseGeneratedNames(expected);
Assert.assertEquals(testName, expected, actual);
} catch (IOException e) {
e.printStackTrace();
} catch (ComparisonFailure e) {
System.err.println("Comparison Failure in " + testName + "!");
System.err.println(expected);
System.err.println(actual);
throw e;
} finally {
if(tmp != null && tmp.exists())
tmp.delete();
}
}
protected CAstEntity parseJS(File tmp, CAstImpl ast) throws IOException {
String moduleName = tmp.getName();
SourceFileModule module = new SourceFileModule(tmp, moduleName, null);
return parseJS(ast, module);
}
protected abstract CAstEntity parseJS(CAstImpl ast, SourceModule module) throws IOException;
// example from the paper
@Test
public void test1() {
testRewriter("function extend(dest, src) {" +
" for(var p in src) {" +
" dest[p] = src[p];" +
" }" +
"}",
"function extend(dest, src) {" +
" for(var p in src) {" +
" (function _forin_body_0(p) {" +
" dest[p] = src[p];" +
" })(p);" +
" }" +
"}");
}
// example from the paper, but with single-statement loop body
@Test
public void test2() {
testRewriter("function extend(dest, src) {" +
" for(var p in src)" +
" dest[p] = src[p];" +
"}",
"function extend(dest, src) {" +
" for(var p in src)" +
" (function _forin_body_0(p) {" +
" dest[p] = src[p];" +
" })(p);" +
"}");
}
// example from the paper, but without var decl
@Test
public void test3() {
testRewriter("function extend(dest, src) {" +
" for(p in src)" +
" dest[p] = src[p];" +
"}",
"function extend(dest, src) {" +
" for(p in src)" +
" (function _forin_body_0(p) {" +
" dest[p] = src[p];" +
" })(p);" +
"}");
}
// example from the paper, but with separate var decl
@Test
public void test4() {
testRewriter("function extend(dest, src) {" +
" var p;" +
" for(p in src)" +
" dest[p] = src[p];" +
"}",
"function extend(dest, src) {" +
" var p;" +
" for(p in src)" +
" (function _forin_body_0(p) {" +
" dest[p] = src[p];" +
" })(p);" +
"}");
}
// example from the paper, but with weirdly placed var decl
@Test
public void test5() {
testRewriter("function extend(dest, src) {" +
" for(p in src) {" +
" var p;" +
" dest[p] = src[p];" +
" }" +
"}",
"function extend(dest, src) {" +
" for(p in src) {" +
" var p;" +
" (function _forin_body_0(p) {" +
" dest[p] = src[p];" +
" })(p);" +
" }" +
"}");
}
// example from the paper, but with weirdly placed var decl in a different place
@Test
public void test6() {
testRewriter("function extend(dest, src) {" +
" for(p in src) {" +
" dest[p] = src[p];" +
" var p;" +
" }" +
"}",
"function extend(dest, src) {" +
" for(p in src) {" +
" var p;" +
" (function _forin_body_0(p) {" +
" dest[p] = src[p];" +
" })(p);" +
" }" +
"}");
}
// example where loop variable is referenced after the loop
// this isn't currently handled, hence the test fails
@Test @Ignore
public void test7() {
testRewriter("function extend(dest, src) {" +
" for(var p in src) {" +
" dest[p] = src[p];" +
" p = true;" +
" }" +
" return p;" +
"}",
"function extend(dest, src) {" +
" for(var p in src)" +
" (function _let_0(_let_parm_0) {" +
" (function _forin_body_0(p) {" +
" try {" +
" dest[p] = src[p];" +
" p = true;" +
" } finally {" +
" _let_parm_0 = p;" +
" }" +
" })(p);" +
" p = _let_parm_0;" +
" })(p);" +
" return p;" +
"}");
}
// example with "this"
@Test
public void test8() {
testRewriter("Object.prototype.extend = function(src) {" +
" for(var p in src)" +
" this[p] = src[p];" +
"}",
"Object.prototype.extend = function(src) {" +
" for(var p in src)" +
" (function _forin_body_0(p, thi$) {" +
" thi$[p] = src[p];" +
" })(p, this);" +
"}");
}
// another example with "this"
@Test
public void test9() {
testRewriter("function defglobals(globals) {" +
" for(var p in globals) {" +
" (function inner() {" +
" this[p] = globals[p];" +
" })();" +
" }" +
"}",
"function defglobals(globals) {" +
" for(var p in globals) {" +
" (function _forin_body_0(p) {" +
" (function inner() {" +
" this[p] = globals[p];" +
" })()" +
" })(p);" +
" }" +
"}");
}
// an example with "break"
@Test
public void test10() {
testRewriter("function extend(dest, src) {" +
" for(var p in src) {" +
" if(p == \"stop\")" +
" break;" +
" dest[p] = src[p];" +
" }" +
"}",
"function extend(dest, src) {" +
" for(var p in src) {" +
" re$ = (function _forin_body_0(p) {" +
" if(p == \"stop\")" +
" return {type: 'goto', target: 0};" +
" dest[p] = src[p];" +
" })(p);" +
" if(re$) {" +
" if(re$.type == 'goto') {" +
" if(re$.target == 0)" +
" break;" +
" }" +
" }" +
" }" +
"}");
}
// another example with "break"
@Test
public void test11() {
testRewriter("function extend(dest, src) {" +
" for(var p in src) {" +
" while(true) {" +
" dest[p] = src[p];" +
" break;" +
" }" +
" }" +
"}",
"function extend(dest, src) {" +
" for(var p in src) {" +
" (function _forin_body_0(p) {" +
" while(true) {" +
" dest[p] = src[p];" +
" break;" +
" }" +
" })(p);" +
" }" +
"}");
}
// an example with labelled "break"
@Test
public void test12() {
testRewriter("function extend(dest, src) {" +
" outer: for(var p in src) {" +
" while(true) {" +
" dest[p] = src[p];" +
" break outer;" +
" }" +
" }" +
"}",
"function extend(dest, src) {" +
" outer: for(var p in src) {" +
" re$ = (function _forin_body_0(p) {" +
" while(true) {" +
" dest[p] = src[p];" +
" return {type: 'goto', target: 0};" +
" }" +
" })(p);" +
" if(re$) {" +
" if(re$.type == 'goto') {" +
" if(re$.target == 0)" +
" break outer;" +
" }" +
" }" +
" }" +
"}");
}
// an example with exceptions
@Test
public void test13() {
testRewriter("function extend(dest, src) {" +
" for(var p in src) {" +
" if(p == '__proto__')" +
" throw new Exception('huh?');" +
" dest[p] = src[p];" +
" }" +
"}",
"function extend(dest, src) {" +
" for(var p in src) {" +
" (function _forin_body_0(p) {" +
" if(p == '__proto__')" +
" throw new Exception('huh?');" +
" dest[p] = src[p];" +
" })(p);" +
" }" +
"}");
}
// an example with a var decl
// this test fails due to a trivial difference between transformed and expected CAst that isn't semantically relevant
@Test @Ignore
public void test14() {
testRewriter("x = 23;" +
"function foo() {" +
" x = 42;" +
" for(var p in {toString : 23}) {" +
" var x = 56;" +
" alert(x);" +
" }" +
" alert(x);" +
"}" +
"foo();" +
"alert(x);",
"x = 23;" +
"function foo() {" +
" x = 42;" +
" for(var p in {toString : 23}) {" +
" var x;" +
" (function _forin_body_0(p) {" +
" x = 56;" +
" alert(x);" +
" })(p);" +
" }" +
" alert(x);" +
"}" +
"foo();" +
"alert(x);");
}
// another example with a var decl
@Test
public void test15() {
testRewriter("x = 23;" +
"function foo() {" +
" x = 42;" +
" for(var p in {toString : 23}) {" +
" (function inner() {" +
" var x = 56;" +
" alert(x);" +
" })();" +
" }" +
" alert(x);" +
"}" +
"foo();" +
"alert(x);",
"x = 23;" +
"function foo() {" +
" x = 42;" +
" for(var p in {toString : 23}) {" +
" (function _forin_body_0(p) {" +
" (function inner() {" +
" var x = 56;" +
" alert(x);" +
" })();" +
" })(p);" +
" }" +
" alert(x);" +
"}" +
"foo();" +
"alert(x);");
}
// an example with a "with" block
@Test
public void test16() {
testRewriter("function extend(dest, src) {" +
" var o = { dest: dest };" +
" with(o) {" +
" for(var p in src) {" +
" dest[p] = src[p];" +
" }" +
" }" +
"}",
"function extend(dest, src) {" +
" var o = { dest: dest };" +
" with(o) {" +
" for(var p in src) {" +
" (function _forin_body_0(p) {" +
" dest[p] = src[p];" +
" })(p);" +
" }" +
" }" +
"}");
}
// top-level for-in loop
@Test
public void test17() {
testRewriter("var o = {x:23};" +
"for(x in o) {" +
" o[x] += 19;" +
"}",
"var o = {x:23};" +
"for(x in o) {" +
" (function _forin_body_0(x) {" +
" o[x] += 19;" +
" })(x);" +
"}");
}
// nested for-in loops
@Test
public void test18() {
testRewriter("var o = {x:{y:23}};" +
"for(x in o) {" +
" for(y in o[x]) {" +
" o[x][y] += 19;" +
" }" +
"}",
"var o = {x:{y:23}};" +
"for(x in o) {" +
" (function _forin_body_0(x) {" +
" for(y in o[x]) {" +
" (function _forin_body_1(y) {" +
" o[x][y] += 19;" +
" })(y);" +
" }" +
" })(x);" +
"}");
}
// return in loop body
@Test
public void test19() {
testRewriter("function foo(x) {" +
" for(var p in x) {" +
" if(p == 'ret')" +
" return x[p];" +
" x[p]++;" +
" }" +
"}",
"function foo(x) {" +
" for(var p in x) {" +
" re$ = (function _forin_body_0(p) {" +
" if(p == 'ret')" +
" return {type: 'return', value: x[p]};" +
" x[p]++;" +
" })(p);" +
" if(re$) {" +
" if(re$.type == 'return')" +
" return re$.value;" +
" }" +
" }" +
"}");
}
// example with two functions
@Test
public void test20() {
testRewriter("function extend(dest, src) {" +
" for(var p in src)" +
" dest[p] = src[p];" +
"}" +
"function foo() {" +
" extend({}, {});" +
"}" +
"foo();",
"function extend(dest, src) {" +
" for(var p in src)" +
" (function _forin_body_0(p) {" +
" dest[p] = src[p];" +
" })(p);" +
"}" +
"function foo() {" +
" extend({}, {});" +
"}" +
"foo();");
}
// example with nested for-in loops and this (adapted from MooTools)
// currently fails because generated names look different
@Test
public void test21() {
testRewriter("function foo() {" +
" var result = [];" +
" for(var style in Element.ShortStyles) {" +
" for(var s in Element.ShortStyles[style]) {" +
" result.push(this.getStyle(s));" +
" }" +
" }" +
"}",
"function foo() {" +
" var result = [];" +
" for(var style in Element.ShortStyles) {" +
" var s;" +
" (function _forin_body_0(style, thi$) {" +
" for(s in Element.ShortStyles[style]) {" +
" (function _forin_body_1(s) {" +
" result.push(thi$.getStyle(s));" +
" })(s);" +
" }" +
" })(style, this);" +
" }" +
"}");
}
// example with nested for-in loops and continue (adapted from MooTools)
@Test
public void test22() {
testRewriter("function foo(property) {" +
" var result = [];" +
" for(var style in Element.ShortStyles) {" +
" if(property != style) continue; " +
" for(var s in Element.ShortStyles[style]) {" +
" ;" +
" }" +
" }" +
"}",
"function foo(property) {" +
" var result = [];" +
" for(var style in Element.ShortStyles) {" +
" var s;" +
" re$ = (function _forin_body_0(style) {" +
" if(property != style) return {type:'goto', target:0}; " +
" for(s in Element.ShortStyles[style]) {" +
" (function _forin_body_1(s) {" +
" ;" +
" })(s);" +
" }" +
" })(style);" +
" if(re$) {" +
" if(re$.type == 'goto') {" +
" if(re$.target == 0)" +
" continue;" +
" }" +
" }" +
" }" +
"}");
}
@Test
public void test23() {
testRewriter("function foo(obj) {" +
" for(var p in obj) {" +
" if(p != 'bar')" +
" continue;" +
" return obj[p];" +
" }" +
"}",
"function foo(obj) {" +
" for(var p in obj) {" +
" re$ = (function _forin_body_0(p) {" +
" if(p != 'bar')" +
" return {type:'goto', target:0};" +
" return {type:'return', value:obj[p]};" +
" })(p);" +
" if(re$) {" +
" if(re$.type == 'return')" +
" return re$.value;" +
" if(re$.type == 'goto') {" +
" if(re$.target == 0)" +
" continue;" +
" }" +
" }" +
" }" +
"}");
}
// currently fails because generated names look different
@Test
public void test24() {
testRewriter("var addSlickPseudos = function() {" +
" for(var name in pseudos)" +
" if(pseudos.hasOwnProperty(name)) {" +
" ;" +
" }" +
"}",
"var addSlickPseudos = function() {" +
" for(var name in pseudos)" +
" (function _forin_body_0(name) {" +
" if(pseudos.hasOwnProperty(name)) {" +
" ;" +
" }" +
" })(name);" +
"}");
}
@Test
public void test25() {
testRewriter("function ext(dest, src) {" +
" for(var p in src)" +
" do_ext(dest, p, src);" +
"}" +
"function do_ext(x, p, y) { x[p] = y[p]; }",
"function ext(dest, src) {" +
" for(var p in src)" +
" (function _forin_body_0(p) {" +
" do_ext(dest, p, src);" +
" })(p);" +
"}" +
"function do_ext(x, p, y) { x[p] = y[p]; }");
}
@Test
public void test26() {
testRewriter("function foo(x) {" +
" for(p in x) {" +
" for(q in p[x]) {" +
" if(b)" +
" return 23;" +
" }" +
" }" +
"}",
"function foo(x) {" +
" for(p in x) {" +
" re$ = (function _forin_body_0(p) {" +
" for(q in p[x]) {" +
" re$ = (function _forin_body_1(q) {" +
" if(b)" +
" return { type: 'return', value: 23 };" +
" })(q);" +
" if(re$) {" +
" return re$;" +
" }" +
" }" +
" })(p);" +
" if(re$) {" +
" if(re$.type == 'return')" +
" return re$.value;" +
" }" +
" }" +
"}");
}
// variation of test22
@Test
public void test27() {
testRewriter("function foo(property) {" +
" var result = [];" +
" outer: for(var style in Element.ShortStyles) {" +
" for(var s in Element.ShortStyles[style]) {" +
" if(s != style) continue outer;" +
" }" +
" }" +
"}",
"function foo(property) {" +
" var result = [];" +
" outer: for(var style in Element.ShortStyles) {" +
" var s;" +
" re$ = (function _forin_body_0(style) {" +
" for(s in Element.ShortStyles[style]) {" +
" re$ = (function _forin_body_1(s) {" +
" if(s != style) return {type:'goto', target:0};" +
" })(s);" +
" if(re$) {" +
" return re$;" +
" }" +
" }" +
" })(style);" +
" if(re$) {" +
" if(re$.type == 'goto') {" +
" if(re$.target == 0)" +
" continue outer;" +
" }" +
" }" +
" }" +
"}");
}
// another variation of test22
@Test
public void test28() {
testRewriter("function foo(property) {" +
" var result = [];" +
" outer: for(var style in Element.ShortStyles) {" +
" for(var s in Element.ShortStyles[style]) {" +
" if(s != style) continue;" +
" }" +
" }" +
"}",
"function foo(property) {" +
" var result = [];" +
" outer: for(var style in Element.ShortStyles) {" +
" var s;" +
" (function _forin_body_0(style) {" +
" for(s in Element.ShortStyles[style]) {" +
" re$ = (function _forin_body_1(s) {" +
" if(s != style) return {type:'goto', target:0};" +
" })(s);" +
" if(re$) {" +
" if(re$.type == 'goto') {" +
" if(re$.target == 0)" +
" continue;" +
" }" +
" }" +
" }" +
" })(style);" +
" }" +
"}");
}
// test where the same entity (namely the inner function "copy") is rewritten more than once
// this probably shouldn't happen
@Test
public void test29() {
testRewriter("Element.addMethods = function(methods) {" +
" function copy() {" +
" for (var property in methods) {" +
" }" +
" }" +
" for (var tag in methods) {" +
" }" +
"};",
"Element.addMethods = function(methods) {" +
" function copy() {" +
" for (var property in methods) {" +
" (function _forin_body_1(property) {" +
" })(property);" +
" }" +
" }" +
" for (var tag in methods) {" +
" (function _forin_body_0(tag){ })(tag);" +
" }" +
"};");
}
@Test
public void test30() {
testRewriter("try {" +
" for(var i in {}) {" +
" f();" +
" }" +
"} catch(_) {}",
"try {" +
" for(var i in {}) {" +
" (function _forin_body_0(i) {" +
" f();" +
" })(i);" +
" }" +
"} catch(_) {}");
}
// cannot extract for-in body referring to "arguments"
@Test
public void test31() {
testRewriter("function extend(dest, src) {" +
" for(var p in src) {" +
" arguments[0][p] = src[p];" +
" }" +
"}",
"function extend(dest, src) {" +
" for(var p in src) {" +
" arguments[0][p] = src[p];" +
" }" +
"}");
}
}